#!/usr/bin/python
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
import os
import string
import sys
import types

import idl_c_proto
import idl_generator
import idl_option
import idl_parser

idl_option.Option('hpp', 'Name of the pass through header file.',
                  default='graphics_translation/gles/pass_through.h')
idl_option.Option('cpp', 'Name of the pass through source file.',
                  default='graphics_translation/gles/pass_through.cpp')
idl_option.Option('dstroot', 'Base directory of output', default='')


_API_MAP = {
    'PPB_OpenGLES2': 'gles2',
    'PPB_OpenGLES2ChromiumMapSub': 'mapsub',
}


def _CreateDirectory(path):
  try:
    os.makedirs(path)
  except OSError as e:
    # Ignore the error iff it is EEXIST.
    if e.errno != errno.EEXIST:
      raise


def _GetLatestStableRelease(interface, releases):
  result = None
  for release in interface.GetUniqueReleases(releases):
    channel = interface.GetProperty('FILE').release_map.GetChannel(release)
    if channel == 'stable':
      result = release
  return result


class PassThroughGenerator(idl_generator.GeneratorByFile):
  _HEADER_FILE_HEADER = string.Template("""
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// *********************************
// *********************************
// AUTOGENERATED CODE.  DO NOT EDIT.
// *********************************
// *********************************

#ifndef ${GUARD_MACRO}
#define ${GUARD_MACRO}

#include <GLES/gl.h>

class GlesContext;
struct PPB_OpenGLES2;
typedef int32_t PP_Resource;

#define PASS_THROUGH(_context, _name, ...) \\
    _name##Call(_context, ##__VA_ARGS__)

""".lstrip())

  _HEADER_FILE_FOOTER = string.Template('#endif  // ${GUARD_MACRO}')

  _SOURCE_FILE_HEADER = string.Template("""
// *********************************
// *********************************
// AUTOGENERATED CODE.  DO NOT EDIT.
// *********************************
// *********************************

#include "${HEADER}"
#include "common/alog.h"
#include "common/trace_event.h"
#include "graphics_translation/gles/debug.h"
#include "graphics_translation/gles/gles_context.h"
#include "graphics_translation/gles/mutex.h"
#include "graphics_translation/gles/underlying_apis.h"
#include "ppapi/cpp/graphics_3d.h"
#include "ppapi/c/ppb_opengles2.h"

static Mutex s_pass_through_lock;

""".lstrip())

  _FUNCTION_TEMPLATE = string.Template("""
${SIGNATURE} {
  pp::Graphics3D* _graphics = static_cast<pp::Graphics3D*>(c->Impl());
  const PepperApis* pepper_apis =
      static_cast<const PepperApis*>(c->Apis());
  const ${INTERFACE}* _api =
      static_cast<const ${INTERFACE}*>(pepper_apis->${API});
  Mutex::Autolock mutex(&s_pass_through_lock);
#ifdef ENABLE_PASSTHROUGH_LOGGING
  ALOGI("${NAME}Call(${LOG_ARGUMENTS_FORMAT_STRING})"${LOG_ARGUMENTS});
#endif
#ifdef ENABLE_PASSTHROUGH_TRACING
  char* args = NULL;
  asprintf(&args, "${TRACE_ARGUMENTS_FORMAT_STRING}"${TRACE_ARGUMENTS});
  TRACE_EVENT2(ARC_TRACE_CATEGORY, "${NAME}",
               "context", _graphics->pp_resource(),
               "args", TRACE_STR_COPY(args));
  free(args);
#endif
  return _api->${NAME}(${ARGS});
}
""".lstrip())

  _TYPE_TO_SIMPLE_LOG_FORMAT = {
      'EGLImageKHR': '%d',
      'GLbitfield': '0x%x',
      'GLboolean': '%d',
      'GLclampf': '%f',
      'GLclampx': '%d',
      'GLdouble': '%f',
      'GLeglImageOES': '%p',
      'GLenum': lambda arg: ('%s (0x%x)', ['GetEnumString(%s)' % arg, arg]),
      'GLfixed': '%d',
      'GLfloat': '%f',
      'GLint': '%d',
      'GLintptr': '%ld',  # TODO(crbug.com/441919): Not size portable!
      'GLsizei': '%zd',
      'GLsizeiptr': '%ld',  # TODO(crbug.com/441919): Not size portable!
      'GLubyte': '%u',
      'GLuint': '%u',
      'const GLchar*': '\\"%s\\"',
      'const GLubyte*': '%p',
      'const GLvoid*': '%p',
  }

  def __init__(self):
    idl_generator.Generator.__init__(
        self, 'Pass through code', 'passthroughgen',
        'Generate the Pass though header and source')

  def GenerateFile(self, filenode, releases, options):
    # We are just interested in PPB_OpenGLES2 and PPB_OpenGLES2ChromiumMapSub,
    # which are in ppb_opengles2.idl.
    if os.path.basename(filenode.GetName()) != 'ppb_opengles2.idl':
      return

    # Generate header/source file content.
    header_path = idl_option.GetOption('hpp')
    guard_macro = header_path.upper().replace('.', '_').replace('/', '_') + '_'

    header_file_header = PassThroughGenerator._HEADER_FILE_HEADER.substitute({
        'GUARD_MACRO': guard_macro
    })
    header_file_footer = PassThroughGenerator._HEADER_FILE_FOOTER.substitute({
        'GUARD_MACRO': guard_macro
    })
    source_file_header = PassThroughGenerator._SOURCE_FILE_HEADER.substitute({
        'HEADER': header_path
    })
    header_file_body, source_file_body = self._GenerateBody(filenode, releases)

    # Output the file contents to files.
    dstroot = idl_option.GetOption('dstroot')
    self._WriteFileContent(
        os.path.join(dstroot, header_path),
        [header_file_header, '\n',
         header_file_body, '\n',
         header_file_footer, '\n'])
    self._WriteFileContent(
        os.path.join(dstroot, idl_option.GetOption('cpp')),
        [source_file_header, '\n', source_file_body, '\n'])

  def _GenerateBody(self, filenode, releases):
    cgen = idl_c_proto.CGen()
    header_body = []
    source_body = []

    for interface in filenode.GetListOf('Interface'):
      if interface.GetName() not in _API_MAP:
        continue
      release = _GetLatestStableRelease(interface, releases)
      for member in interface.GetListOf('Member'):
        if not member.InReleases([release]):
          continue
        return_type, name, arrayspec, callspec = cgen.GetComponents(
            member, release, 'ref')
        # Replace the first argument "PP_Resource context" by
        # "const GlesContext* c".
        callspec = callspec[:]
        callspec[0] = ('const GlesContext*', 'c', [], None)
        signature = cgen.Compose(
            return_type, name + 'Call', arrayspec, callspec,
            '', False, True, False)
        header_body.append(signature + ';')

        # Build arguments, which is as same as |callspec|, except
        # that the first argument is "_graphics->pp_resource()".
        args = ['_graphics->pp_resource()']
        args.extend(arg for _, arg, _, _ in callspec[1:])

        log_args_format_string, log_args = (
            self._generate_args_format_string_and_args(name, callspec))
        trace_args_format_string, trace_args = (
            self._generate_args_format_string_and_args(name, callspec[1:]))

        source_body.append(PassThroughGenerator._FUNCTION_TEMPLATE.substitute({
            'SIGNATURE': signature,
            'INTERFACE': interface.GetName(),
            'API': _API_MAP[interface.GetName()],
            'NAME': name,
            'ARGS': ', '.join(args),
            'LOG_ARGUMENTS_FORMAT_STRING': log_args_format_string,
            'LOG_ARGUMENTS': log_args,
            'TRACE_ARGUMENTS_FORMAT_STRING': trace_args_format_string,
            'TRACE_ARGUMENTS': trace_args,
        }))

    return '\n'.join(header_body), '\n\n'.join(source_body)

  def _generate_args_format_string_and_args(self, api_name, args):
    format_arguments = []
    format_string = []

    for arg in args:
      arg_type = arg[0]
      arg_names = [arg[1]]
      assert arg_names[0]

      arg_format = self._TYPE_TO_SIMPLE_LOG_FORMAT.get(arg_type)
      if arg_format is not None:
        if isinstance(arg_format, (types.FunctionType, types.LambdaType)):
          arg_format, arg_names = arg_format(*arg_names)
        format_string.append(arg_format)
        format_arguments.extend(arg_names)
      elif arg_type.endswith('*'):
        format_string.append('%p')
        format_arguments.extend(arg_names)
      else:
        assert False, 'Unhandled type %s for argument %s for API %s' % (
            arg_type, arg_names, api_name)

    if format_arguments:
      format_string = ', '.join(format_string)
      format_arguments = ', ' + ', '.join(format_arguments)
    else:
      format_string = '%s'
      format_arguments = ', ""'
    return format_string, format_arguments

  def _WriteFileContent(self, path, content):
    _CreateDirectory(os.path.dirname(path))
    with open(path, 'w') as stream:
      stream.writelines(content)


_pass_through_gen = PassThroughGenerator()


def main():
  filenames = idl_option.ParseOptions(sys.argv[1:])
  ast = idl_parser.ParseFiles(filenames)
  assert not ast.errors, ast.errors
  return idl_generator.Generator.Run(ast)

if __name__ == '__main__':
  main()
